package model;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import model.CommonValues.paymentMethods;
import model.CommonValues.permissions;
import model.database.DBException;
import model.database.Database;

public class SystemDataManager implements Model {

	private Database database; // used to contact system's database 
	
	/**
	 * Constructor- creates a new manager for system's data
	 * @param database- database to work with
	 */
	public SystemDataManager(Database database) {
		
		// set given database
		this.database = database;
	}
	
	/**
	 * Uses database to add employee according to given details.
	 * Returns True if could add employee, False if not.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public boolean addEmployee(int number, String name, String userName, permissions permission)
			throws DBException {
		
		/*
		 * add employee according to given details and return indicator 
		 * for success
		 */
		return database.addEmployee(number, name, userName, permission);
	}

	/**
	 * Uses database to add customer account according to given details.
	 * Returns True if could add customer, False if not.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public boolean createCustomerAccount(int number, String name,
			String address, paymentMethods paymentMethod) throws DBException {
				
		/*
		 * add customer according to given details and return indicator 
		 * for success
		 */
		return database.createCustomerAccount(number, name, address,
				paymentMethod);
	}

	/**
	 * Uses database to add user record according to given details.
	 * Returns True if could add user, False if not.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public boolean addUserRecord(String userName, permissions permission)
			throws DBException {
		
		// add user according to given details and return indicator for success
		return database.addUserRecord(userName, permission);
	}

	/**
	 * Uses database to add new product according to given details.
	 * Returns True if could add product, False if not.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public boolean addNewProduct(String name, double price) throws DBException {
		
		/*
		 * add product according to given details and return indicator 
		 * for success
		 */
		return database.addNewProduct(name, price);
	}

	/**
	 * Uses database to add order according to given details.
	 * Returns True if could add order, False if not.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public boolean addNewOrder(int orderNumber, String productName, int amount,
			int customerNumber) throws DBException {
		
		/*
		 * add order according to given details and return indicator for success
		 */
		return database.addNewOrder(orderNumber, productName, amount,
				customerNumber);
	}

	/**
	 * Uses database to update price of product matching given name.
	 * Returns True if could update price, False if not.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public boolean updateProductPrice(String name, double newPrice)
			throws DBException {
		
		// update product's price and return indicator for success
		return database.updateProductPrice(name, newPrice);
	}

	/**
	 * Uses database to update details of customer matching given number.
	 * Returns True if could update customer details, False if not.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public boolean updateCustomerDetails(int number, String name,
			String address, paymentMethods paymentMethod) throws DBException {

		// update customer's details and return indicator for success
		return database.updateCustomerDetails(number, name, address, paymentMethod);
	}

	/**
	 * Uses database to set given password to given user. 
	 * Password is set only if correct (old) password is given.
	 * Throws DBException if had problems connecting to database
	 */
	public boolean setPassword(String userName, String oldPassword, String newPassword)
			throws DBException {
		
		return database.setPassword(userName, oldPassword, newPassword);
	}
	
	/**
	 * Uses database to get parameters of each product in system.
	 * Creates Product objects according to retrieved details and
	 * returns the products created.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public List<Product> getProducts() throws DBException {

		// get all products details
		List<Map<String,String>> productsDetails = database.getProducts();
		
		// create Product objects according to given details
		List<Product> products = createProducts(productsDetails);
		
		// return retrieved products
		return products;
	}

	/**
	 * Gets list of parameters maps of products and creates Product objects accordingly.
	 * Returns list of products created
	 * @param productsDetails- list of parameters map for each product
	 * @return list of products matching given parameters maps
	 * @throws DBException if could not parse data correctly
	 */
	private List<Product> createProducts(List<Map<String, String>> productsDetails) 
			throws DBException {
		
		// allocate list to hold the new products
		List<Product> products = new LinkedList<Product>();

		// scan all parameters maps
		int numOfProducts = productsDetails.size();
		for (int i = 0; i < numOfProducts; ++i) {

			// get current product's parameters
			Map<String, String> currProductParams = productsDetails.get(i);

			// extract current product's name
			String name = currProductParams.get(CommonValues.NAME);

			// extract current product's price
			double price = 0;
			try {
				price = Double.parseDouble(currProductParams
						.get(CommonValues.PRICE));
			} catch (NumberFormatException e) {
				// database error
				throw new DBException("Could not retrieve products- database error!"); // TODO CHECK IF HANDLES ERROR CORRECTLY
			}

			// create product with extracted attributes
			Product currProduct = new Product(name, price);

			// add to list
			products.add(currProduct);
		}

		// return products found
		return products;
	}
	
	/**
	 * Uses database to get price of product matching given name.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public double getProductPrice(String productName) throws DBException {
		
		// use database to get product's price
		return database.getProductPrice(productName);
	}

	/**
	 * Uses database to get all employee's details and permissions,
	 * and creates employees according to retrieved data.
	 * Uses the permission to decide which type of employee
	 * should create.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public List<Employee> getEmployees() throws DBException {
		
		// get all employees details
		List<Map<String, String>> employeesDetails = database.getEmployees();
		
		// get permissions of all store's employees
		List<CommonValues.permissions> permissions = database.getEmployeesPermissions();
		
		// create employees according to details got
		List<Employee> employees = createEmplooyes(employeesDetails, permissions);
		
		// return retrieved employees
		return employees;
	}

	/**
	 * Gets list of parameters maps of employees and creates Employee objects accordingly.
	 * Uses factory to create correct type of employee.
	 * Returns list of products created
	 * @param employeesDetails- list of parameters map for each employee
	 * @return list of employees matching given parameters maps
	 * @throws DBException if could not parse data correctly
	 */
	private List<Employee> createEmplooyes(
			List<Map<String, String>> employeesDetails,
			List<CommonValues.permissions> permissions) throws DBException {
		
		// allocate factory to create employees
		EmployeeFactory factory = new EmployeeFactory();
		
		// allocate list to hold the new employees
		List<Employee> employees = new LinkedList<Employee>();

		// scan all parameters maps
		int numOfEmployees = employeesDetails.size();
		for (int i = 0; i < numOfEmployees; ++i) {

			// get current employee's parameters
			Map<String, String> currEmployeeParams = employeesDetails.get(i);

			// extract current employee's name and user name
			String name = currEmployeeParams.get(CommonValues.NAME);
			String userName = currEmployeeParams.get(CommonValues.USER_NAME);

			// extract current employee's number
			int number = 0;
			try {
				number = Integer.parseInt(currEmployeeParams.get(CommonValues.NUMBER));
			} catch (NumberFormatException e) {
				// database error
				throw new DBException("Could not retrieve employees- database error!"); // TODO CHECK IF HANDLES ERROR CORRECTLY
			}

			// create product with extracted attributes
			Employee currEmployee = factory.createNewEmployee(permissions.get(i), number, name, userName);

			// add to list
			employees.add(currEmployee);
		}

		// return products found
		return employees;
	}
	
	/**
	 * Uses database to get parameters of each customer in system.
	 * Creates Customer objects according to retrieved details and
	 * returns the customers created.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public List<Customer> getCustomers() throws DBException {
		
		// get all customers details from database
		List<Map<String, String>> customersDetails = database.getCustomers();
		
		// create customers according to details got
		List<Customer> customers = createCustomers(customersDetails);

		// return retrieved customers
		return customers;
	}

	/**
	 * Uses database to get parameters of all customers that have ordered
	 * product matching given name.
	 * Creates Customer objects according to retrieved details and
	 * returns the customers created.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public List<Customer> getCustomers(String productName) throws DBException {
		
		// get details of specified customers
		List<Map<String, String>> customersDetails = database.getCustomers(productName);
		
		// create customers according to details got
		List<Customer> customers = createCustomers(customersDetails);

		// return retrieved customers
		return customers;
	}

	/**
	 * Uses database to get parameters of all customers that have ordered
	 * sum to more than given value.
	 * Creates Customer objects according to retrieved details and
	 * returns the customers created.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public List<Customer> getCustomers(double totalSum) throws DBException {
		
		// get details of specified customers
		List<Map<String, String>> customersDetails = database.getCustomers(totalSum);
		
		// create customers according to details got
		List<Customer> customers = createCustomers(customersDetails);

		// return retrieved customers
		return customers;
	}
	
	/**
	 * Gets list of parameters maps of customers and creates Customer objects accordingly.
	 * Returns list of customers created
	 * @param customersDetails- list of parameters map for each customer 
	 * @return list of customers matching given parameters maps
	 * @throws DBException if could not parse data correctly
	 */
	private List<Customer> createCustomers(List<Map<String, String>> customersDetails) 
			throws DBException {
		
		// allocate list to hold the new customers
		List<Customer> customers = new LinkedList<Customer>();

		// scan all parameters maps
		int numOfCustomers = customersDetails.size();
		for (int i = 0; i < numOfCustomers; ++i) {

			// get current customer's parameters
			Map<String, String> currCustomerParams = customersDetails.get(i);

			// extract current customer's number
			int number = 0;
			try {
				number = Integer.parseInt(currCustomerParams.get(CommonValues.NUMBER));
			} catch (NumberFormatException e) {
				// database error
				throw new DBException("Could not retrieve customers- database error!"); // TODO CHECK IF HANDLES ERROR CORRECTLY
			}
			
			// extract current customer's payment method
			int paymentMethod = 0;
			try {
				paymentMethod = Integer.parseInt(currCustomerParams.get(CommonValues.PAYMENT_METHOD));
			} catch (NumberFormatException e) {
				// database error
				throw new DBException("Could not retrieve customers- database error!"); // TODO CHECK IF HANDLES ERROR CORRECTLY
			}
			
			// check if extracted numeric value is valid
			if (paymentMethod < 1 || paymentMethod > CommonValues.permissions.values().length) {
				
				// database error
				throw new DBException("Could not retrieve customers- database error!");
			}
			
			// extract current customer's name, user-name and address
			String name = currCustomerParams.get(CommonValues.NAME);
			String userName = currCustomerParams.get(CommonValues.USER_NAME);
			String address = currCustomerParams.get(CommonValues.ADDRESS);

			// create customer with extracted attributes
			Customer currCustomer = new Customer(number, name, userName, address,
					CommonValues.paymentMethods.values()[paymentMethod - 1]);

			// add to list
			customers.add(currCustomer);
		}

		// return customers found
		return customers;
	}
	
	/**
	 * Uses database to get parameters of each order in system.
	 * Creates Order objects according to retrieved details and
	 * returns the orders created.
	 * Throws DBException if had problems connecting to database
	 */
	@Override
	public List<Order> getOrders(int customerNumber) throws DBException {

		// get all orders details
		List<Map<String,String>> ordersDetails = database.getProducts();
		
		// create Order objects according to given details
		List<Order> orders = createOrders(ordersDetails);
		
		// return retrieved orders
		return orders;
	}

	/**
	 * Gets list of parameters maps of orders and creates Order objects accordingly.
	 * Returns list of orders created
	 * @param ordersDetails- list of parameters map for each order 
	 * @return list of orders matching given parameters maps
	 * @throws DBException if could not parse data correctly
	 */
	private List<Order> createOrders(List<Map<String, String>> ordersDetails)
			throws DBException {
		
		// allocate list to hold the orders
		List<Order> orders = new LinkedList<Order>();

		// scan all parameters maps
		int numOfOrders = ordersDetails.size();
		for (int i = 0; i < numOfOrders; ++i) {

			// get current orders's parameters
			Map<String, String> currOrderParams = ordersDetails.get(i);

			// extract current order's number
			int orderNum = 0;
			try {
				orderNum = Integer.parseInt(currOrderParams.get(CommonValues.NUMBER));
			} catch (NumberFormatException e) {
				// database error
				throw new DBException("Could not retrieve orders- database error!"); // TODO CHECK IF HANDLES ERROR CORRECTLY
			}
			
			// extract number of customer that ordered current order
			int orderingCustomerNum = 0;
			try {
				orderingCustomerNum = Integer.parseInt(currOrderParams.get(CommonValues.ORDERING_CUSTOMER_NUMBER));
			} catch (NumberFormatException e) {
				// database error
				throw new DBException("Could not retrieve orders- database error!"); // TODO CHECK IF HANDLES ERROR CORRECTLY
			}

			// extract amount of ordered product
			int amount = 0;
			try {
				amount = Integer.parseInt(currOrderParams.get(CommonValues.AMOUNT));
			} catch (NumberFormatException e) {
				// database error
				throw new DBException("Could not retrieve orders- database error!"); // TODO CHECK IF HANDLES ERROR CORRECTLY
			}
			
			// extract current order's total sum
			double totalSum = 0;
			try {
				totalSum = Double.parseDouble(currOrderParams.get(CommonValues.AMOUNT));
			} catch (NumberFormatException e) {
				// database error
				throw new DBException("Could not retrieve orders- database error!"); // TODO CHECK IF HANDLES ERROR CORRECTLY
			}
			
			// extract current order's date and ordered product's name
			String date = currOrderParams.get(CommonValues.DATE);
			String productName = currOrderParams.get(CommonValues.NAME);

			// create order with extracted attributes
			Order currOrder = new Order(orderNum, date, orderingCustomerNum, productName, amount, totalSum);

			// add to list
			orders.add(currOrder);
		}

		// return customers found
		return orders;
	}
	
	/**
	 * Uses database to return report (total value of sales) of sales made
	 * on given date.
	 * Throws DBException if had problems connecting to database 
	 */
	@Override
	public double getSalesReport(String date) throws DBException {
		
		// return sales report for given date
		return database.getSalesReport(date);
	}

	/**
	 * Uses database to return report (total value of sales) of sales made
	 * in given month.
	 * Throws DBException if had problems connecting to database 
	 */
	@Override
	public double getSalesReport(int month) throws DBException {

		// return sales report for given month
		return database.getSalesReport(month);
	}
}